using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using CodePlex.DnD4eCampaignManager.DnD4e.Enums;
using CodePlex.DnD4eCampaignManager.DnD4e.Interfaces;

namespace CodePlex.DnD4eCampaignManager.DnD4e
{
    public class Monster : Combatant, IMonster, IEquatable<Monster>
    {
        public Monster(IAbilities abilities, IDefenses defenses, IHitPoints hitPoints, IMovement movement, ISenses senses, IInitiative initiative)
        {
            _healingSurges = new HealingSurges(hitPoints);
            _movement = movement;
            _abilities = abilities;
            _defenses = defenses;
            _senses = senses;
            _initiative = initiative;
        }

        private Monster() : this(new Abilities(), new Defenses(), new HitPoints(), new Movement(), new Senses(), new Initiative())
        {
            //The existance of this constructor is a consession to the XmlSerializer in the persistence layer.
            //Yes, it makes me feel dirty.  Thanks for asking.
        }

        public override string DisplayName
        {
            get
            {
                if ( Index.HasValue )
                {
                    return string.Format("{0} {1}", Name, Index.GetValueOrDefault());
                }
                return Name;
            }
        }

        public string AttackPowers { get; set; }
        public bool Elite { get; set; }
        public string Equipment { get; set; }

        public int ExperiencePointValue
        {
            get { return CalculateExperiencePointValue(Level, Elite, Solo, Role); }
        }

        public int? Index { get; set; }

        public string Keywords { get; set; }
        public bool Leader { get; set; }
        public int Level { get; set; }
        public string Origin { get; set; }
        public MonsterRole Role { get; set; }
        public int SavingThrows { get; set; }
        public bool Solo { get; set; }

        public override bool Equals(object obj)
        {
            if ( ReferenceEquals(null, obj) )
            {
                return false;
            }
            if ( ReferenceEquals(this, obj) )
            {
                return true;
            }
            if ( obj.GetType() != typeof ( Monster ) )
            {
                return false;
            }
            return Equals((Monster) obj);
        }

        public override string FormatCombatantForDisplay()
        {
            int totalHitPoints = HitPointsCurrent + HitPointsTemporary;
            string temporaryHitPoints = string.Empty;
            if ( HitPointsTemporary > 0 )
            {
                temporaryHitPoints = string.Format(" ({0} temp)", HitPointsTemporary);
            }
            string movementSeperator = string.IsNullOrEmpty(MovementSpeedOther) ? string.Empty : ",";
            return string.Format("{0} - {1}{7}/{2}; AC: {3}, Fort: {4}, Ref: {5}, Will: {6}; Speed: {8}{10} {9}", DisplayName, totalHitPoints,
                                 HitPointsMaximum, DefenseArmorClass, DefenseFortitude, DefenseReflex, DefenseWillpower, temporaryHitPoints,
                                 MovementSpeed,
                                 MovementSpeedOther, movementSeperator);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int result = Level;
                result = ( result * 397 ) ^ ( AttackPowers != null ? AttackPowers.GetHashCode() : 0 );
                result = ( result * 397 ) ^ Elite.GetHashCode();
                result = ( result * 397 ) ^ ( Equipment != null ? Equipment.GetHashCode() : 0 );
                result = ( result * 397 ) ^ ( Index.HasValue ? Index.Value : 0 );
                result = ( result * 397 ) ^ ( Keywords != null ? Keywords.GetHashCode() : 0 );
                result = ( result * 397 ) ^ Leader.GetHashCode();
                result = ( result * 397 ) ^ ( Origin != null ? Origin.GetHashCode() : 0 );
                result = ( result * 397 ) ^ Role.GetHashCode();
                result = ( result * 397 ) ^ SavingThrows;
                result = ( result * 397 ) ^ Solo.GetHashCode();
                return result;
            }
        }

        public override string ToString()
        {
            MemoryStream memoryStream = new MemoryStream();
            XmlSerializer xmlSerializer = new XmlSerializer(GetType());
            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF7)
                                              {
                                                  Formatting = Formatting.Indented,
                                                  Indentation = 1,
                                                  IndentChar = '\t'
                                              };
            xmlSerializer.Serialize(xmlTextWriter, this);
            memoryStream = (MemoryStream) xmlTextWriter.BaseStream;
            UTF7Encoding encoding = new UTF7Encoding();
            return encoding.GetString(memoryStream.ToArray()).Replace("><", ">\r\n<");
        }

        public bool Equals(Monster obj)
        {
            if ( ReferenceEquals(null, obj) )
            {
                return false;
            }
            if ( ReferenceEquals(this, obj) )
            {
                return true;
            }
            return obj.Level == Level && Equals(obj.AttackPowers, AttackPowers) && obj.Elite.Equals(Elite) && Equals(obj.Equipment, Equipment) &&
                   obj.Index.Equals(Index) && Equals(obj.Keywords, Keywords) && obj.Leader.Equals(Leader) && Equals(obj.Origin, Origin) &&
                   Equals(obj.Role, Role) && obj.SavingThrows == SavingThrows && obj.Solo.Equals(Solo);
        }

        public static bool operator ==(Monster left, Monster right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(Monster left, Monster right)
        {
            return !Equals(left, right);
        }

        public static int CalculateExperiencePointValue(int level, bool elite, bool solo, MonsterRole monsterRole)
        {
            int standard = 100;
            switch ( level )
            {
                case 2:
                    standard = 125;
                    break;
                case 3:
                    standard = 150;
                    break;
                case 4:
                    standard = 175;
                    break;
                case 5:
                    standard = 200;
                    break;
                case 6:
                    standard = 250;
                    break;
                case 7:
                    standard = 300;
                    break;
                case 8:
                    standard = 350;
                    break;
                case 9:
                    standard = 400;
                    break;
                case 10:
                    standard = 500;
                    break;
                case 11:
                    standard = 600;
                    break;
                case 12:
                    standard = 700;
                    break;
                case 13:
                    standard = 800;
                    break;
                case 14:
                    standard = 1000;
                    break;
                case 15:
                    standard = 1200;
                    break;
                case 16:
                    standard = 1400;
                    break;
                case 17:
                    standard = 1600;
                    break;
                case 18:
                    standard = 2000;
                    break;
                case 19:
                    standard = 2400;
                    break;
                case 20:
                    standard = 2800;
                    break;
                case 21:
                    standard = 3200;
                    break;
                case 22:
                    standard = 4150;
                    break;
                case 23:
                    standard = 5100;
                    break;
                case 24:
                    standard = 6050;
                    break;
                case 25:
                    standard = 7000;
                    break;
                case 26:
                    standard = 9000;
                    break;
                case 27:
                    standard = 11000;
                    break;
                case 28:
                    standard = 13000;
                    break;
                case 29:
                    standard = 15000;
                    break;
                case 30:
                    standard = 19000;
                    break;
                case 31:
                    standard = 23000;
                    break;
                case 32:
                    standard = 27000;
                    break;
                case 33:
                    standard = 31000;
                    break;
                case 34:
                    standard = 39000;
                    break;
                case 35:
                    standard = 47000;
                    break;
                case 36:
                    standard = 55000;
                    break;
                case 37:
                    standard = 63000;
                    break;
                case 38:
                    standard = 79000;
                    break;
                case 39:
                    standard = 95000;
                    break;
                case 40:
                    standard = 111000;
                    break;
            }
            if ( elite )
            {
                return standard * 2;
            }
            if ( solo )
            {
                return standard * 5;
            }
            if ( monsterRole == MonsterRole.Minion )
            {
                return (int) Math.Round(standard * .25m, 0, MidpointRounding.AwayFromZero);
            }
            return standard;
        }
    }
}